﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.Serialization;

namespace InstantSpine.Core.MaterialModels
{
    /// <summary>
    /// Hyperelastic Neo-Hookean solid material
    /// </summary>
    [DataContract]
    public sealed class NeoHookeanMaterial: MaterialViewModel
    {
        private double? c10;
        private double? d1;

        private double? apparentYoungsModulus;
        private double? apparentPoissonsRatio;

        bool suppressUpdate = false;

        // read/write for two-way binding
        /// <summary>
        /// Shear stiffness parameter C10 = G / 2 (G = shear modulus)
        /// </summary>
        [DataMember]
        public double? C10 { 
            get { return c10; }
            set {
                if (c10 != value) {
                    c10 = value;
                    OnPropertyChanged();
                    if (!suppressUpdate)
                        UpdateApparentLinearElasticProperties();
                }
            }
        }

        // read/write for two-way binding
        /// <summary>
        /// Compressibility parameter D1 = 2 / K (K = bulk modulus)
        /// </summary>
        [DataMember]
        public double? D1 {
            get { return d1; }
            set {
                if (d1 != value) {
                    d1 = value;
                    OnPropertyChanged();
                    if (!suppressUpdate)
                        UpdateApparentLinearElasticProperties();
                }
            }
        }

        void UpdateApparentLinearElasticProperties() {
            suppressUpdate = true;
            if (c10 != null && d1 != null) {                
                ApparentYoungsModulus = (18 * c10) / (3 + c10 * d1);                
                ApparentPoissonsRatio = (3 - 2 * c10 * d1) / (6 + 2 * c10 * d1);
            }
            suppressUpdate = false;
        }

        public double? ApparentYoungsModulus {
            get { return apparentYoungsModulus; }
            set {
                if (apparentYoungsModulus != value) {
                    apparentYoungsModulus = value;
                    OnPropertyChanged();
                    if (!suppressUpdate)
                        UpdateNeoHookeanFromIsoLinElastic();
                }
            }
        }

        public double? ApparentPoissonsRatio {
            get { return apparentPoissonsRatio; }
            set {
                if (apparentPoissonsRatio != value) {
                    apparentPoissonsRatio = value;
                    OnPropertyChanged();
                    if (!suppressUpdate)
                        UpdateNeoHookeanFromIsoLinElastic();
                }
            }
        }

        void UpdateNeoHookeanFromIsoLinElastic() {
            suppressUpdate = true;
            if (apparentYoungsModulus != null && apparentPoissonsRatio != null) {
                C10 = apparentYoungsModulus / (4 * (1 + apparentPoissonsRatio));
                D1 = (6 - 12 * apparentPoissonsRatio) / apparentYoungsModulus;
            }
            suppressUpdate = false;
        }

        /// <summary>
        /// Create a new Neo-Hookean solid
        /// </summary>
        /// <param name="c10">Shear stiffness parameter</param>
        /// <param name="d1">Compressibility parameter</param>
        public NeoHookeanMaterial(double c10, double d1, string name): base(name) {
            this.c10 = c10;
            this.d1 = d1;
        }

        /// <summary>
        /// Create a "dummy" material object, i.e. C10 = 0, D1 = 0
        /// </summary>
        public NeoHookeanMaterial(string name = ""): base(name) {
            this.c10 = null;
            this.d1 = null;
        }

        // required for XML serialization to work
        public NeoHookeanMaterial() : this("") { }

        /// <summary>
        /// Create a Neo-Hookean material based on given IsoLinElastic material properties
        /// </summary>
        /// <param name="iso">Isotropic linear elastic material properties</param>
        /// <returns>A corresponding Neo-Hookean material model</returns>
        public static NeoHookeanMaterial FromIsoLinearElastic(IsoLinearElasticMaterial iso) {
            // c10 = Emod/(4*(1+nu))
            // d1 = (6 - 12 * nu) / Emod
            return new NeoHookeanMaterial(
                c10:  iso.YoungsModulus.Value / (4 * (1 + iso.PoissonsRatio.Value)),
                d1:   (6 - 12 * iso.PoissonsRatio.Value) / iso.YoungsModulus.Value,
                name: iso.Name);
        }        
    }    
}
